package main

import (
	"fmt"
	"reflect"
)

//反射的最佳实践(1)

//使用反射来遍历结构体的字段，调用结构体的方法，并且获取结构体标签的值
//相关两个重要的的方法：
//1、func (v Value) Call(in []Value) []Value     传入参数和返回参数是[]reflect.Value
//2、func (v Value) Method(i int) Value         默认按方法名排序对应i值，i从0开始
func main() {
	//创建一个Monster实例
	var m Monster = Monster{
		Name:  "黄鼠狼",
		Age:   400,
		Score: 38.9,
	}
	//将实例传入函数
	TestStruct(m)
}

type Monster struct {
	Name  string `json:"name"`
	Age   int    `json:"monster_age"`
	Score float64
	Sex   string
}

func (m Monster) Print() {
	fmt.Println("---start---")
	fmt.Println("---", m, "---")
	fmt.Println("---end---")
}
func (m Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}
func (m Monster) Set(name string, age int, score float64, sex string) {
	m.Name = name
	m.Age = age
	m.Score = score
	m.Sex = sex
}

func TestStruct(a interface{}) {
	//获取reflect.Type类型
	typ := reflect.TypeOf(a)
	//获取reflect.Value类型
	val := reflect.ValueOf(a)
	//获取到a对应的类别
	kd := val.Kind()
	if kd != reflect.Struct { //判断是不是结构体  ，比较的时候使用常量的比较，reflect包下的Struct
		fmt.Println("expect struct")
		return
	}

	//获取到该结构体有几个字段？
	num := val.NumField()
	fmt.Println("struct has ", num, " fields")
	//遍历结构体的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("Field %d: Value is %v\n", i, val.Field(i)) //val.Field(i)返回的是第i个字段的值的Value封装
		//获取到struct标签，注意需要通过reflect.Type来获取tag标签的值  typ.Field(i)返回struct类型的第i个字段的类型，如非结构体或者i不在[0, NumField())内将会panic
		tagVal := typ.Field(i).Tag.Get("json") //Get方法是Tag字段的结构体类型中的一个方法 //func (tag StructTag) Get(key string) string
		if tagVal != "" {                      //判断是否有标签
			fmt.Printf("Field %d : tag is %v\n", i, tagVal)
		}
	}
	//获取该结构体绑定了多少个方法
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)

	//在对方法排序的时候，是先对所有函数进行排序，按照函数名的ASCII码大小进行排序，所以第2个方法是Print（），与定义方法时的顺序无关
	val.Method(1).Call(nil) //获取第2个方法并且调用

	//调用结构体的第1个方法Method（0）
	var params []reflect.Value                   //声明一个Value切片
	params = append(params, reflect.ValueOf(10)) //将常量10转成了Value类型，然后加入切片
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是 []reflect.Value  返回的还是[]reflect.Value
	fmt.Println("res=", res[0].Int()) //因为调用的是加法方法，返回一个int类型，这里的res[0]是返回结果，是Value类型，通过Int()方法就可以转成int类型
}

/*
type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径，对导出字段该字段为""。
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}
*/
